/*
 *  Copyright (C) 2011 JiangHongTiao <jjurco.sk_gmail.com>
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package sk.lieskove.jianghongtiao.multithreaddownloader.document;

import info.monitorenter.cpdetector.io.*;
import org.apache.log4j.Logger;
import org.mozilla.universalchardet.UniversalDetector;

import java.io.*;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.HashMap;
import java.util.Map;

/**
 * Date of create: May 15, 2011
 *
 * @author JiangHongTiao <jjurco.sk_gmail.com>
 * @version 2011.0515
 */
public class RemoteFileEncoding {

    private final CodepageDetectorProxy detector = CodepageDetectorProxy.getInstance();
    private Logger log = Logger.getLogger(RemoteFileEncoding.class.getName());

    {
        synchronized (detector) {
            detector.add(new ByteOrderMarkDetector());
            detector.add(new ParsingDetector(true));
            detector.add(UnicodeDetector.getInstance());
            detector.add(JChardetFacade.getInstance()); // Another singleton.
            detector.add(ASCIIDetector.getInstance()); // Fallback, see javadoc.
        }
    }

    /**
     * try to recognize encoding from file
     * @param is Input file stream. InputStream have to support mark.
     * @param length length of the input stream
     * @return detected charset
     * @throws IOException throws when there is an exception thrown while reading file
     */
    public Charset fileEncoding(File document) throws FileNotFoundException, IOException {
        Charset charset = null;
        BufferedInputStream is = new BufferedInputStream(new FileInputStream(document));
        synchronized (detector) {
            try {
                charset = detector.detectCodepage(is, (int) document.length());
            } catch (IllegalArgumentException iae) {
                log.error("Stream reading exception? ", iae);
            } catch (IOException ioe) {
                log.error("Cannot read source file: " + document.getAbsolutePath());
            }
        }
        if ((charset == null) || (charset == UnknownCharset.getInstance())) {
            charset = UnknownCharset.getInstance();
        }
        return charset;
    }

    /**
     * try to get file encoding from server
     * @param connection connection to server with remote file
     * @return relevant encoding if server returns encoding type, otherwise null
     */
    public Charset getRemoteEncoding(HttpURLConnection connection) {
        Charset result = null;
        String encoding = connection.getContentEncoding();
        if ((encoding != null) && (!encoding.equals(""))
                && (Charset.isSupported(encoding))) {
            result = Charset.forName(encoding);
        }
        return result;
    }

    public Charset makeEncoding(String encoding) {
        Charset result = null;
        if ((encoding != null) && (!encoding.equals(""))
                && (Charset.isSupported(encoding))) {
            result = Charset.forName(encoding);
        }
        return result;
    }

    public Charset guessEncoding(File file, String serverMimeType,
            String remoteContentEncoding) {
        Charset server = null;
        Charset mimeType = null;
        Charset fileEnc = null;
        Charset juniversalchardet = null;
        if (serverMimeType != null) {
            mimeType = tryParseMimeType(serverMimeType);
        }
        if ((file != null) && (file.exists())) {
            try {
                fileEnc = fileEncoding(file);
                juniversalchardet = jUniversalChardet(file);
//                return fileEncoding(file);
            } catch (IOException ex) {
                log.error("I/O exception thrown while trying to guess document "
                        + "encoding");
            }
        }
        if (remoteContentEncoding != null) {
            try {
                if (Charset.isSupported(remoteContentEncoding)) {
                    server = Charset.forName(remoteContentEncoding);
                }
            } catch (IllegalCharsetNameException ex) {
                log.info("Illegal charset: " + remoteContentEncoding);
            }
        }
        try {
            Charset ch = selectCharset(server, mimeType, fileEnc, juniversalchardet);
            if (ch != null) {
                return ch;
            }
        } catch (TooManyEncodings ex) {
            log.info("Too many encodings were detected!");
        }
        if ((fileEnc != null) && (!fileEnc.equals(UnknownCharset.getInstance()))) {
            return fileEnc;
        }
        if (juniversalchardet != null) {
            return juniversalchardet;
        }
        if (server != null) {
            return server;
        }
        if (mimeType != null) {
            return mimeType;
        }
        return null;
    }

//    public Charset jChardet(File file) {
//        BufferedInputStream imp = null;
//        try {
//            nsDetector det = new nsDetector(nsPSMDetector.ALL);
//            // Set an observer...
//            // The Notify() will be called when a matching charset is found.
//            det.Init(new nsICharsetDetectionObserver() {
//
//                @Override
//                public void Notify(String charset) {
//                    HtmlCharsetDetector.found = true;
//                    System.out.println("CHARSET = " + charset);
//                }
//            });
//            imp = new BufferedInputStream(new FileInputStream(file));
//            byte[] buf = new byte[1024];
//            int len;
//            boolean done = false;
//            boolean isAscii = true;
//            while ((len = imp.read(buf, 0, buf.length)) != -1) {
//
//                // Check if the stream is only ascii.
//                if (isAscii) {
//                    isAscii = det.isAscii(buf, len);
//                }
//
//                // DoIt if non-ascii and not done yet.
//                if (!isAscii && !done) {
//                    done = det.DoIt(buf, len, false);
//                    HtmlCharsetDetector.found = true;
//                }
//            }
//            det.DataEnd();
//            if(done){
//                if(det.getProbableCharsets().length > 0){
//                    
//                }
//                return makeEncoding(null)
//            }
//        } catch (FileNotFoundException ex) {
//            java.util.logging.Logger.getLogger(RemoteFileEncoding.class.getName()).log(Level.SEVERE, null, ex);
//        } catch (IOException ex) {
//            java.util.logging.Logger.getLogger(RemoteFileEncoding.class.getName()).log(Level.SEVERE, null, ex);
//        } finally {
//            try {
//                imp.close();
//            } catch (IOException ex) {
//                java.util.logging.Logger.getLogger(RemoteFileEncoding.class.getName()).log(Level.SEVERE, null, ex);
//            }
//        }
//    }
    public Charset jUniversalChardet(File file) {
        java.io.FileInputStream fis = null;
        try {
            byte[] buf = new byte[4096];
            fis = new java.io.FileInputStream(file);
            // (1)
            UniversalDetector det = new UniversalDetector(null);
            // (2)
            int nread;
            while ((nread = fis.read(buf)) > 0 && !det.isDone()) {
                det.handleData(buf, 0, nread);
            }
            // (3)
            det.dataEnd();
            // (4)
            String encoding = det.getDetectedCharset();
            // (5)
            det.reset();

            return makeEncoding(encoding);
        } catch (FileNotFoundException ex) {
            log.error("File was not found: " + file.getAbsolutePath());
        } catch (IOException ex) {
            log.error("IO Exception during reading file: " + file.getAbsolutePath());
        } finally {
            try {
                fis.close();
            } catch (IOException ex) {
                log.error("Cannot close file: " + file.getAbsolutePath());
            }
        }
        return null;

    }

    /**
     * try to parse mime type and extract charset. Charset of file is usually
     * returned by server as part of mime type.
     * @param mimeType mime type to parse
     * @return if extraction is successful, then relevant charset, otherwise null
     */
    public Charset tryParseMimeType(String mimeType) {
        //test for removing: " ;
        //known: repeating encoding
        //occuring encodings:
        //utf-8, iso-8859-1, us-ascii, windows-1252,latin-1,windows-1255,windows-1256,
        if (mimeType == null) {
            throw new NullPointerException("Mime-type was null!");
        }
        Charset result = null;
        mimeType = mimeType.toLowerCase().replaceAll("\\\"", "");
        int charsetPos = mimeType.indexOf("charset=");
        if (charsetPos >= 0) {
            String charset = mimeType.substring(charsetPos + 8);
            //take the longest string which can be recognized as encoding
            if ((charset != null) && (!charset.equals(""))) {
                int cut = charset.length();

                while ((cut > 0) && (result == null)) {
                    String subcharset = charset.substring(0, cut);
                    try {
                        if (Charset.isSupported(subcharset)) {
                            result = Charset.forName(subcharset);
                        }
                    } catch (IllegalCharsetNameException e) {
                        log.debug("Charset: " + subcharset + " is not supported.");
                    }
                    cut--;
                }
            }
        }
        return result;
    }

    /**
     * compare charsets and returns common charset
     * @param charsets list of charsets to check
     * @return not-null encoding if is present, otherwise null
     * @throws TooManyEncodings throws when more then one encoding type is present
     */
    public Charset selectCharset(Charset... charsets) throws
            TooManyEncodings {
        Map<Charset, Integer> counter = new HashMap<Charset, Integer>();
        for (int i = 0; i < charsets.length; i++) {
            Charset charset = charsets[i];
            if ((charset != null) && (!charset.equals(UnknownCharset.getInstance()))) {
                //if there is such encoding, just increse number of occurences
                if (counter.containsKey(charset)) {
                    Integer num = counter.get(charset);
                    num++;
                    counter.put(charset, num);
                } else {
                    //when counter does not contains charset, put new one
                    counter.put(charset, 0);
                }
            }
        }
        if (counter.isEmpty()) {
            return null;
        }
        if (counter.size() == 1) {
            return (Charset) counter.keySet().toArray()[0];
        } else {
            //find maximal occurences or throws an exception?
            throw new TooManyEncodings("Too many encodings (" + counter.size()
                    + ").");
        }
    }
}
